package com.dss.auto_deploy.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dss.auto_deploy.config.WebsocketEndPoint;
import com.dss.auto_deploy.constants.AutoConstants;
import com.dss.auto_deploy.domain.AdmDb;
import com.dss.auto_deploy.domain.AdmDocker;
import com.dss.auto_deploy.domain.AdmNacos;
import com.dss.auto_deploy.domain.Server;
import com.dss.auto_deploy.dto.AdmNacosAddDto;
import com.dss.auto_deploy.dto.AdmNacosPageDto;
import com.dss.auto_deploy.dto.AdmNacosUpdateDto;
import com.dss.auto_deploy.dto.WsSendDto;
import com.dss.auto_deploy.mapper.AdmDockerMapper;
import com.dss.auto_deploy.mapper.AdmNacosMapper;
import com.dss.auto_deploy.service.IAdmNacosService;
import com.dss.auto_deploy.service.IServerService;
import com.dss.auto_deploy.util.*;
import com.dss.auto_deploy.vo.AdmNacosVo;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Container;
import com.jcraft.jsch.Session;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.xmlbeans.ResourceLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;


/**
* @Description:nacos配置Service实现类
* @author dongshoushan
* @date 2022-10-22 21:48:11
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class AdmNacosServiceImpl  extends ServiceImpl<AdmNacosMapper,AdmNacos> implements IAdmNacosService {

    /**
    *
    * nacos配置Mapper
    */
    private final AdmNacosMapper admNacosMapper;
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private final RemoteSshUtil remoteSshUtil;
    private final AdmDockerMapper admDockerMapper;
    private final IServerService serverService;
    private final WebsocketEndPoint websocketEndPoint;
    private final DockerUtil dockerUtil;
    private final CacheUtils cacheUtils;
    /**
    * 新增nacos配置
    * @param admNacosAddDto
    * @return Long
    */
    @Override
    public int insert(AdmNacosAddDto admNacosAddDto){
        AdmNacos admNacos = BeanUtils.copy(admNacosAddDto, AdmNacos.class);
        admNacos.setCreateTime(new Date());
        int insert = admNacosMapper.insert(admNacos);
        if (insert == 1) {
            log.info("新增成功{}",JacksonUtils.toString(admNacos));
        }
        cacheUtils.deleteObject("getAllAdmNacos");
        return insert;
    }


    /**
    * 修改nacos配置
    * @param admNacosUpdateDto
    * @return Long
    */
    @Override
    public int update(AdmNacosUpdateDto admNacosUpdateDto){
        AdmNacos admNacos = BeanUtils.copy(admNacosUpdateDto, AdmNacos.class);
        int number = admNacosMapper.updateById(admNacos);
        if (number == 1) {
            log.info("修改成功{}",JacksonUtils.toString(admNacos));
        }
        cacheUtils.deleteObject("getAllAdmNacos");
        return number;
    }


    /**
    * 批量删除nacos配置
    * @param ids
    * @return Long
    */
    @Override
    public void deleteBatch(Long[] ids){
        this.removeByIds(Arrays.asList(ids));
        cacheUtils.deleteObject("getAllAdmNacos");
    }

    /**
    * nacos配置分页查询
    *
    * @param pageDto
    * @return List<AdmNacos>
    */
    @Override
    public Page<AdmNacosVo> list(AdmNacosPageDto pageDto) throws InterruptedException {
        Page<AdmNacos> tablesPage = new Page<>(pageDto.getPage(), pageDto.getLimit());
        Page<AdmNacos> pageSelectResult = admNacosMapper.selectPage(tablesPage, Wrappers.lambdaQuery(AdmNacos.class)
                .like(StringUtils.isNotEmpty(pageDto.getContent()),AdmNacos::getNacosName, pageDto.getContent())
        );
        List<AdmNacos> records = pageSelectResult.getRecords();
        List<AdmNacosVo> voList = convertToVo(records);
        Page<AdmNacosVo> page = new Page<>();
        page.setRecords(voList);
        page.setTotal(pageSelectResult.getTotal());
        page.setSize(pageSelectResult.getSize()); 
        return page;
    }

    @Override
    public List<AdmNacosVo> convertToVo(List<AdmNacos> list) throws InterruptedException {
        if (CollectionUtil.isEmpty(list)){
            return new ArrayList<>();
        }
        List<AdmNacosVo> admNacosVos = BeanUtils.copyList(list, AdmNacosVo.class);
        List<Long> dockerIds = list.stream().map(AdmNacos::getDockerId).collect(Collectors.toList());
        List<Long> dbIds = list.stream().map(AdmNacos::getDbId).collect(Collectors.toList());
        List<Server> serverList = serverService.getAll();
        List<AdmDb> admDbList = SpringUtils.getBean(AdmDbServiceImpl.class).getAll().stream().filter(s -> dbIds.contains(s.getId())).collect(Collectors.toList());
        List<AdmDocker> dockerList = SpringUtils.getBean(AdmDockerServiceImpl.class).getAll().stream().filter(s->dockerIds.contains(s.getId())).collect(Collectors.toList());
        Map<String, List<Container>> containerMap = SpringUtils.getBean(AdmDockerServiceImpl.class).getContainerMap(dockerList);
        admNacosVos.forEach(vo -> {
            AdmDocker admDocker = dockerList.stream().filter(s -> s.getId().equals(vo.getDockerId())).findFirst().orElse(null);
            if (admDocker == null){
                vo.setStateName("未绑定docker");
            }else {
                List<Container> containers = containerMap.get("AdmDockerVo" + admDocker.getId());
                Container container = containers.stream().filter(s -> s.getId().equals(vo.getContainerId())).findFirst().orElse(null);
                if (container == null) {
                    vo.setStateName("未创建或超时");
                } else {
                    vo.setStateName(container.getState());
                }
                vo.setDockerName(admDocker.getDockerName());
                Server server = serverList.stream().filter(s -> s.getId().equals(admDocker.getServerId())).findFirst().orElse(null);
                vo.setIp(server.getIp());
                vo.setServerId(server.getId());
            }
            AdmDb admDb = admDbList.stream().filter(s -> s.getId().equals(vo.getDbId())).findFirst().orElse(null);
            if (admDb != null){
                vo.setDbName(admDb.getName());
                Server server = serverList.stream().filter(s -> s.getId().equals(admDb.getServerId())).findFirst().orElse(null);
                vo.setDbUrl(StringUtils.format("{}:{}", server.getIp(), admDb.getPort()));
            }
        });
        return admNacosVos;
    }

    @Override
    public List<AdmNacos> getAll() {
        if (cacheUtils.hasKey("getAllAdmNacos")) {
            return cacheUtils.getCacheObject("getAllAdmNacos");
        }else {
            List<AdmNacos> list = admNacosMapper.selectList(null);
            cacheUtils.setCacheObject("getAllAdmNacos", list,60*60*1000*24);
            return list;
        }
    }

    private final String baseNacosLocalPath = System.getProperty("user.dir") + "/temp/config/nacos";
    /**
     * 安装
     * @param ids
     * @param tempPath
     * @param nacosFile
     */
    @Override
    public void installnacos(String ids, String tempPath, MultipartFile nacosFile,MultipartFile plugFile) {
        long fileSize = nacosFile.getSize();
        String[] split = ids.split(",");
        CountDownLatch mainLatch = new CountDownLatch(split.length);
        for (String id : split) {
            threadPoolTaskExecutor.execute(() -> {
                //上传包
                try {
                    AdmNacos nacos = admNacosMapper.selectById(id);
                    AdmDb admDb = SpringUtils.getBean(AdmDbServiceImpl.class).getById(nacos.getDbId());
                    String mountPath = nacos.getMountPath();
                    if (nacos.getDockerId() == null) {
                        return;
                    }
                    AdmDocker admDocker = admDockerMapper.selectById(nacos.getDockerId());
                    Server serverDocker =  serverService.getServerByDockerId(admDocker.getId());
                    //创建nacos的数据库表
                    createNacosDb(nacos,admDb);
                    HashMap<String, Object> map = getNacosConfigData(nacos,admDb);
                    String configLocalPath = baseNacosLocalPath + "/" + nacos.getId() + "/application.properties";
                    FileUtils.generateFileByTemplate("nacos.ftl", configLocalPath, baseNacosLocalPath + "/" + nacos.getId(), map);
                    Session session = remoteSshUtil.connect(serverDocker);
                    remoteSshUtil.putFile(session, configLocalPath, mountPath+"/conf");
                    System.out.println("开始上传nacos镜像文件");
                    //关闭防火墙
                    remoteSshUtil.exec(AutoConstants.DISABLE_FIREWALLD, session);
                    remoteSshUtil.exec(AutoConstants.STOP_FIREWALLD, session);
                    remoteSshUtil.exec(AutoConstants.AUTHORIZATION_FOLDER + tempPath, session);
                    remoteSshUtil.exec(StringUtils.format("mkdir -p {}/logs {}/conf",mountPath), session);
                    CountDownLatch mainLatch2 = new CountDownLatch(1);
                    CountDownLatch mainLatch3 = new CountDownLatch(1);
                    threadPoolTaskExecutor.execute(() -> {
                        try {
                            remoteSshUtil.putFile(session, nacosFile, tempPath, fileSize, Long.parseLong(id), "nacosInstall",serverDocker.getIp());
                        } catch (Exception e) {
                            throw new ServiceException(e.getMessage());
                        } finally {
                            mainLatch2.countDown();
                        }
                    });
                    threadPoolTaskExecutor.execute(() -> {
                        try {
                            if ("postgres".equals(nacos.getDbType()) && !plugFile.isEmpty()){
                                remoteSshUtil.putFile(session, plugFile, mountPath+"/plugins", fileSize, Long.parseLong(id), "nacosInstall",serverDocker.getIp());
                            }
                        } catch (Exception e) {
                            throw new ServiceException(e.getMessage());
                        } finally {
                            mainLatch3.countDown();
                        }
                    });
                    mainLatch2.await();
                    mainLatch3.await();
                    sendMsg("installing", nacosFile.getOriginalFilename(), Long.parseLong(id));
                    DockerClient dockerClient = dockerUtil.getDockerClientByDocker(SpringUtils.getBean(AdmDockerServiceImpl.class).convertToVo(admDocker));
                    dockerUtil.delContainer(dockerClient, "nacos");
                    sendMsg("loading", nacosFile.getOriginalFilename(), Long.parseLong(id));
                    //加载到镜像
                    remoteSshUtil.exec(StringUtils.format("docker load -i {}/{}", tempPath, nacosFile.getOriginalFilename()), session);
                    //安装mysql
                    String info = StringUtils.format("docker  run \\\n" +
                            "--name nacos -d \\\n" +
                            "-p {}:8848 \\\n" +
                            "-p 9848:9848 \\\n" +
                            "-p 9849:9849 \\\n" +
                            "--privileged=true \\\n" +
                            "--restart=always \\\n" +
                            "-v /etc/localtime:/etc/localtime \\\n" +
                            "-e MODE=standalone \\\n" +
                            "-e PREFER_HOST_MODE=hostname \\\n" +
                            "-v {}/logs:/home/nacos/logs \\\n" +
                            "-v {}/plugins:/home/nacos/plugins \\\n" +
                            "-v {}/conf/application.properties:/home/nacos/conf/application.properties \\\n" +
                            "nacos/nacos-server",nacos.getNacosPort(),  mountPath,mountPath,mountPath);
                    System.out.println(info);
                    String exec = remoteSshUtil.exec(info, session);
                    System.out.println(exec);
                    nacos.setContainerId(exec.replace("\n", ""));
                    nacos.setCreateTime(new Date());
                    this.updateById(nacos);
                    sendMsg("starting", nacosFile.getOriginalFilename(), Long.parseLong(id));
                    Thread.sleep(10 * 1000);
                    sendMsg("complete", nacosFile.getOriginalFilename(), Long.parseLong(id));
                    remoteSshUtil.close(session);
                    dockerUtil.close(dockerClient);
                } catch (Exception e) {
                    e.printStackTrace();
                    alertFailMsg(e.getMessage());
                } finally {
                    mainLatch.countDown();
                }
            });
        }
        try {
            mainLatch.await();
            System.out.println("nacos全部安装完毕");
            alertSuccessMsg("completeAll");
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
    }

    private void createNacosDb(AdmNacos nacos, AdmDb admDb) throws Exception {
        if ("mysql".equals(nacos.getDbType())){
            Connection conn = SpringUtils.getBean(AdmDbServiceImpl.class).getMysqlConnection(admDb);
            PreparedStatement ps = conn.prepareStatement(StringUtils.format("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{}';", nacos.getDatabaseName()));
            ResultSet rs = ps.executeQuery();
            List list = new ArrayList<String>();
            ResultSetMetaData rsmd = rs.getMetaData();
            int ColumnCount;
            while (rs.next()) {
                ColumnCount = rsmd.getColumnCount();
                Map<String, Object> rowData = new HashMap<>();
                for (int i = 1; i <= ColumnCount; i++) {
                    rowData.put(rsmd.getColumnName(i), rs.getObject(i));
                }
                //将ResultSet结果集写入list
                list.add(rowData);
                System.out.println(rowData);
            }
            ps.close();
            if (list.isEmpty() || list.size() == 0){
                conn.prepareStatement(StringUtils.format("CREATE DATABASE `{}`", nacos.getDatabaseName())).execute();
            }
            conn.close();
            //初始化sql
        }else {
            Connection conn = SpringUtils.getBean(AdmDbServiceImpl.class).getPostgresConnection(admDb);
            PreparedStatement ps = conn.prepareStatement(StringUtils.format("SELECT datname FROM pg_catalog.pg_database WHERE datname = '{}';", nacos.getDatabaseName()));
            ResultSet rs = ps.executeQuery();
            List list = new ArrayList<String>();
            ResultSetMetaData rsmd = rs.getMetaData();
            int ColumnCount;
            while (rs.next()) {
                ColumnCount = rsmd.getColumnCount();
                Map<String, Object> rowData = new HashMap<>();
                for (int i = 1; i <= ColumnCount; i++) {
                    rowData.put(rsmd.getColumnName(i), rs.getObject(i));
                }
                //将ResultSet结果集写入list
                list.add(rowData);
                System.out.println(rowData);
            }
            ps.close();
            if (list.isEmpty() || list.size() == 0){
                conn.prepareStatement(StringUtils.format("CREATE DATABASE \"{}\";", nacos.getDatabaseName())).execute();
            }
            conn.close();
        }
        //init sql
        initSql(nacos,admDb);
    }

    private void initSql(AdmNacos nacos, AdmDb admDb) throws Exception {
        // 获取资源的输入流
        if ("mysql".equals(nacos.getDbType())){
            InputStream inputStream = ResourceLoader.class.getResourceAsStream("/nacos/nacos-init-mysql.sql");
            String s = FileUtils.readFromInputStream(inputStream);
            Connection conn = getMysqlNacosConnection(admDb, nacos.getDatabaseName());
            conn.prepareStatement(s).execute();
            conn.close();
        }else {
            InputStream inputStream = ResourceLoader.class.getResourceAsStream("/nacos/nacos-init-postgres.sql");
            String s = FileUtils.readFromInputStream(inputStream);
            Connection conn = getPostgresNacosConnection(admDb, nacos.getDatabaseName());
            conn.prepareStatement(s).execute();
            conn.close();
        }

    }

    public Connection getMysqlNacosConnection(AdmDb admDb,String databaseName) throws Exception {
        Server server = serverService.getServerById(admDb.getServerId());
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection(StringUtils.format("jdbc:mysql://{}:{}/{}?useUnicode=true&allowMultiQueries=true" +
                "&characterEncoding=UTF-8&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false&useSSL=false&allowPublicKeyRetrieval=true", server.getIp(), admDb.getPort(),databaseName)
                , admDb.getUsername(), admDb.getPassword());
        return connection;
    }
    public Connection getPostgresNacosConnection(AdmDb admDb,String databaseName) throws Exception {
        Server server = serverService.getServerById(admDb.getServerId());
        Class.forName("org.postgresql.Driver");
        Connection connection = DriverManager.getConnection(StringUtils.format("jdbc:postgresql://{}:{}/{}?useUnicode=true&allowMultiQueries=true" +
                        "                &characterEncoding=UTF-8&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false&useSSL=false", server.getIp(), admDb.getPort(),databaseName)
                , admDb.getUsername(), admDb.getPassword());
        return connection;
    }
    private HashMap<String, Object> getNacosConfigData(AdmNacos nacos,AdmDb admDb) {
        Server server = serverService.getAll().stream().filter(s -> s.getId().equals(admDb.getServerId())).findFirst().orElse(null);
        HashMap<String, Object> map = new HashMap<>();
        map.put("user", admDb.getUsername());
        map.put("password", admDb.getPassword());
        if ("mysql".equals(admDb.getDbType())){
            map.put("sqlType","mysql");
            map.put("driverClassName","com.mysql.cj.jdbc.Driver");
            map.put("url",StringUtils.format("jdbc:mysql://{}:{}/{}?useUnicode=true&rewriteBatchedStatements=true" +
                    "&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true",
                    server.getIp(),admDb.getPort(),nacos.getDatabaseName()));
        }else {
            map.put("sqlType","postgresql");
            map.put("driverClassName","org.postgresql.Driver");
            map.put("url",StringUtils.format("jdbc:postgresql://{}:{}/{}?currentSchema=public" +
                            "&reWriteBatchedInserts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai",
                    server.getIp(),admDb.getPort(),nacos.getDatabaseName()));
        }
        return map;
    }

    private void sendMsg(String status, String name, Long id) {
        websocketEndPoint.send(WsSendDto.builder().type(0).table("nacosInstall").status(status).name(name).id(id).build());
    }

    /**
     * 弹窗提示
     *
     * @param data
     */
    private void alertSuccessMsg(String data) {
        websocketEndPoint.send(WsSendDto.builder().type(2).table("nacosInstall").data(data).build());
    }

    /**
     * 弹窗提示
     *
     * @param data
     */
    private void alertFailMsg(String data) {
        websocketEndPoint.send(WsSendDto.builder().type(3).table("nacosInstall").data(data).build());
    }

}
